import {CommonTool} from './common.tool';

/**
 * 上传数据
 */
export class UploadData {
  public file: any; // 文件
  public fileName?: string;
  public folderPath: string; // 目录路径
  public resumed: any; // 开启断点续传
}

/**
 * 上传token
 */
export class UploadToken {
  public token: string; // 用户token
  public folderId: string; // 文件夹id
}

/**
 * 签名信息
 */
export class UploadSign {
  public appId: ''; // AppId
  public sign: ''; // 签名信息
  public randomStr: ''; // 随机字符串
  public timeStamp: ''; // 时间戳
  public fixedPath: ''; // 固定路径，必须存在，网盘创建
}

/**
 * 上传配置
 */
export class UploadConfig {
  public uploadUrl: string; // 上传的url地址
  public isHttp?: any = true; // 是否发送http请求
  public token?: UploadToken; // token信息
  public sign?: UploadSign; // 签名信息
  public verifyStop?: any = function (data: UploadData, reader: FileReader, that: XcUploadTool) {
    // 验证是否暂停
    // data：传入的数据对象
    // reader：加载对象为空表示上传暂停
    // that: this对象
    return false;
  };
  public loadingStart?: any = function (current: number, data: UploadData, that: XcUploadTool) {
    // 开始加载（计算md5）
    // current：当前进度
    // data：传入的数据对象
    // that: this对象
  };
  public loadingProgress?: any = function (current: number, data: UploadData, that: XcUploadTool) {
    // 加载（计算md5）进度
    // current：当前进度
    // data：传入的数据对象
    // that: this对象
  };
  public loadingSuccess?: any = function (md5: string, current: number, data: UploadData, that: XcUploadTool) {
    // 加载（计算md5）完成
    // md5：计算出的md5
    // data：传入的数据对象
    // current：当前进度
    // that: this对象
  };
  public uploadStart?: any = function (current: number, data: UploadData, that: XcUploadTool) {
    // 开始上传
    // current：当前进度
    // data：传入的数据对象
    // that: this对象
  };
  public uploadProgress?: any = function (current: number, data: UploadData, that: XcUploadTool) {
    // 上传进度
    // current：当前进度
    // data：传入的数据对象
    // that: this对象
  };
  public uploadSuccess?: any = function (current: number, data: UploadData, res: any, that: XcUploadTool) {
    // 上传完成
    // current：当前进度
    // data：传入的数据对象
    // res：请求返回对象
    // that: this对象
  };
  public uploadFail?: any = function (data: UploadData, res: any, that: XcUploadTool) {
    // 上传失败
    // current：当前进度
    // data：传入的数据对象
    // res：请求返回对象
    // that: this对象
  };
}

export class XcUploadTool {
  public config: UploadConfig;
  public data: UploadData;
  public reader: FileReader; // 读取文件对象
  public current: number; // 当前值
  public md5: string; // md5值
  public uploadUrl: string; // 上传的url地址
  public stopUpload: any = false; // 暂停上传
  public renewalSize: any = 10485760; // 每次计算hash文件读取大小
  public httpRequest: XMLHttpRequest;

  constructor(config: UploadConfig, data: UploadData) {
    this.config = config;
    this.data = data;
  }

  /**
   * 获取内容中两个字符之间的值
   *  content 内容
   *  start 开始符号
   *  end 结束符号
   */
  public static getMidValue(content, start, ends) {
    const httpUrls = [];
    if (content && content.indexOf(start) !== -1) {
      const strArray = [];
      const array = content.split(start).slice(1);
      for (const arr of array) {
        const str = start + arr.split(')')[0];
        strArray.push(str);
      }
      for (const arr of strArray) {
        for (const end of ends) {
          const suffix = '.' + end;
          if (arr.indexOf(suffix) !== -1) {
            httpUrls.push(arr.split(suffix)[0] + suffix);
          }
        }
      }
    }
    return httpUrls;
  }

  /**
   * 随机文件名
   */
  public static randomFileName(name) {
    const suffix = name.substring(name.lastIndexOf('.'), name.length);
    const fileName = CommonTool.dateFormat(new Date(), 'YYYYMMDDhhmmss') + CommonTool.randomNum(1000, 9999);
    return fileName + suffix;
  }

  /**
   * 处理文件目录
   */
  public static handleFileDirectory(dataTransfer) {
    const that = this;
    dataTransfer = dataTransfer || {};
    const items = dataTransfer.items;
    if (items && items.length && (items[0].webkitGetAsEntry || items[0].getAsEntry)) {
      const array = [];
      for (const item of items) {
        if (item.webkitGetAsEntry) {
          const entry = item.webkitGetAsEntry();
          if (entry) {
            entry._file = item.getAsFile();
          }
          array.push(entry);
        } else {
          array.push(item.getAsEntry());
        }
      }
      return new Promise((resolve, reject) => {
        that.handleDirectoryTree(array, [], resolve, reject);
      });
    }
    return new Promise((resolve, reject) => {
      const array = [];
      for (const file of dataTransfer.files) {
        array.push(file);
      }
      resolve(array);
    });
  }

  /**
   * 递归处理文件树
   */
  public static handleDirectoryTree(entries, array, resolve, reject) {
    let bool = true;
    for (const entry of entries) {
      if (entry.isFile) {
        array.push(entry);
      } else {
        bool = false;
        const dirReader = entry.createReader();
        this.readEntries(dirReader, array, [], resolve, reject);
      }
    }
    if (bool) {
      const fileList = [];
      for (const entry of array) {
        if (entry._file) {
          Object.defineProperty(entry._file, 'webkitRelativePath', {
            value: entry['fullPath'],
            writable: false,
            enumerable: true,
            configurable: true
          });
          fileList.push(entry._file);
          if (array.length === fileList.length) {
            resolve(fileList);
          }
        } else {
          entry.file((file) => {
            Object.defineProperty(file, 'webkitRelativePath', {
              value: entry['fullPath'],
              writable: false,
              enumerable: true,
              configurable: true
            });
            fileList.push(file);
            if (array.length === fileList.length) {
              resolve(fileList);
            }
          }, (e) => {
            console.error(e);
            reject(e);
          });
        }
      }
    }
  }

  /**
   * 读取文件目录下的文件信息
   * 每次读取100条
   */
  public static readEntries(dirReader, array, dirList, resolve, reject) {
    const that = this;
    dirReader.readEntries((results) => {
      if (!results.length) {
        that.handleDirectoryTree(dirList, array, resolve, reject);
      } else {
        dirList = dirList.concat(results);
        that.readEntries(dirReader, array, dirList, resolve, reject);
      }
    }, (e) => {
      console.error(e);
      reject(e);
    });
  }


  /**
   * 同步上传
   */
  public asyncUpload() {
    const that = this;
    return new Promise((resolve, reject) => {
      this.getMd5(that.data, (md5, isStop) => {
        console.log(isStop)
        if (!isStop) {
          that.uploadFile(that.data, that.config, md5, that.config.uploadUrl, null, null, () => {
            resolve();
          });
        } else {
          resolve();
        }
      });
    });
  }

  /**
   * 上传
   */
  public upload() {
    const that = this;
    that.getMd5(that.data, (md5, isStop) => {
      if (!isStop) {
        that.uploadFile(that.data, that.config, md5, that.config.uploadUrl, null, null, null);
      }
    });
  }

  /**
   * 获取文件hash值
   */
  public getMd5(data: UploadData, fun) {
    const that = this;
    that.current = 0;
    if (that.config.loadingStart) {
      that.config.loadingStart(that.current, data, this);
    }
    const file = data.file;
    that.reader = new FileReader();
    // @ts-ignore
    const SparkMD5 = require('spark-md5');
    const spark = new SparkMD5.ArrayBuffer();
    that.reader.readAsArrayBuffer(file.slice(that.current, that.current + that.renewalSize));
    that.reader.onload = (e) => {
      if (that.config.verifyStop ? that.config.verifyStop(data, that.reader, this) : that.stopUpload) {
        fun(null, true);
        return false;
      }
      spark.append(e.target['result']);
      if (file.size <= that.current) {
        const md5 = spark.end();
        if (that.config.loadingSuccess) {
          that.config.loadingSuccess(md5, that.current, data, this);
        }
        that.md5 = md5;
        fun(md5, false);
      } else {
        that.current += that.renewalSize;
        if (file.size <= that.current) {
          that.current = file.size;
        }
        that.reader.readAsArrayBuffer(file.slice(that.current, that.current + that.renewalSize));
        if (that.config.loadingProgress) {
          that.config.loadingProgress(that.current, data, this);
        }
      }
    };
    that.reader.onabort = () => {
      fun(null, true);
    };
    that.reader.onerror = () => {
      fun(null, true);
    }
  }

  /**
   * 上传文件
   */
  public uploadFile(data: UploadData, config: UploadConfig, md5, uploadUrl, current, renewalSize, fun) {
    if (!renewalSize) {
      renewalSize = 0;
    }
    if (!current) {
      current = 0;
    }
    const that = this;
    if (config.uploadStart) {
      config.uploadStart(current, data, this);
    }
    if (!config.isHttp) {
      return;
    }
    const file = data.file;
    let fileName = data.fileName;
    if (!data.fileName) {
      fileName = file.name;
    }
    // 上传文件
    const formData = new FormData();
    const blob = file.slice(current, current + renewalSize);
    formData.append('file', blob);
    formData.append('name', fileName);
    formData.append('hashCode', md5);
    formData.append('fileIndex', current);
    formData.append('size', file.size);
    formData.append('folderPath', data.folderPath);
    formData.append('resumed', data.resumed);
    // 签名上传
    if (config.sign) {
      formData.append('appId', config.sign.appId);
      formData.append('sign', config.sign.sign);
      formData.append('randomStr', config.sign.randomStr);
      formData.append('timeStamp', config.sign.timeStamp);
      formData.append('fixedPath', config.sign.fixedPath);
    }
    this.httpRequest = new XMLHttpRequest();
    this.httpRequest.open('POST', uploadUrl);
    if (config.token) {
      formData.append('folderId', config.token.folderId);
      this.httpRequest.setRequestHeader('token', config.token.token);
    }
    this.httpRequest.upload.addEventListener('progress', (evt) => {
      if (!data.resumed) {
        if (evt.loaded === evt.total) {
          if (fun) {
            fun();
          }
          if (config.uploadSuccess) {
            config.uploadSuccess(evt.loaded, data, null, that);
          }
        } else {
          if (config.uploadProgress) {
            config.uploadProgress(evt.loaded, data, that);
          }
        }
      }
    }, false);
    this.httpRequest.onloadend = (e) => {
      if (config.verifyStop ? config.verifyStop(data, null, this) : that.stopUpload) {
        if (fun) {
          fun();
        }
        return false;
      }
      if (e.target['status'] === 200) {
        if (e.target['response']) {
          const res = JSON.parse(e.target['response']);
          if (res.finish) {
            if (fun) {
              fun();
            }
            if (config.uploadSuccess) {
              config.uploadSuccess(res.fileIndex, data, res, that);
            }
          } else {
            if (config.uploadProgress) {
              config.uploadProgress(res.fileIndex, data, that);
            }
            that.uploadUrl = res['uploadUrl'];
            that.uploadFile(data, config, md5, res['uploadUrl'], res.fileIndex, res.renewalSize, fun);
          }
        }
      } else if (e.target['status'] === 400) {
        console.log(e.target['response']);
        if (config.uploadFail) {
          config.uploadFail(data, e.target['response'], that);
        }
      } else {
        console.log(e.target['response']);
        if (config.uploadFail) {
          config.uploadFail(data, e.target['response'], that);
        }
      }
      return false;
    };
    this.httpRequest.onabort = () => {
      if (fun) {
        fun();
      }
    };
    this.httpRequest.send(formData);
  }

  /**
   * 重试
   */
  public retry() {
    const uploadUrl = this.uploadUrl ? this.uploadUrl : this.config.uploadUrl;
    this.uploadFile(this.data, this.config, this.md5, uploadUrl, null, null, null);
  }

  /**
   * 开始
   */
  public start() {
    this.stopUpload = false;
    if (!this.md5) {
      if (this.reader) {
        this.reader.readAsArrayBuffer(this.data.file.slice(this.current, this.current + this.renewalSize));
      } else {
        this.upload();
      }
    } else {
      const uploadUrl = this.uploadUrl ? this.uploadUrl : this.config.uploadUrl;
      this.uploadFile(this.data, this.config, this.md5, uploadUrl, null, null, null);
    }
  }

  /**
   * 暂停
   */
  public stop() {
    this.stopUpload = true;
    if (this.httpRequest) {
      this.httpRequest.abort();
    } else {
      if (this.reader) {
        this.reader.abort();
      }
    }
  }
}
